
----------------------------------------------------------------
--- Copyright：  迷你玩-海马
--- Description：数据缓存服务器
----------跨服务器的ssdb数据cache层-----------------------------
----------将数据从逻辑服中分离出来，并提供共享内存加速----------
----------------------------------------------------------------

---接口
---read    读取数据 (默认加远程锁 可选不加)   key+hash  get value
---save    保存数据 (默认解远程锁 可选不解)   key+hash  set value
---del     删除数据 (别名hdel)                key+hash  del value

---mread   批量读取数据                      (key1+hash1 key2+hash2 key3+hash3)

---hlen           获得hash的参数
---hlist          按key获取所有hash值，不带有数据
---hget_all       按key获取所有hash数据
---clean_cache    直接清理cache

---set_kv         保存kv数据，不带hash  ( 有时间限制的数据，一般0到366天，主要用来排重 )
---get_kv         获得kv数据，不带hash
---del_kv         删除kv数据，不带hash  ( 暂未实现，可set_kv expire_time=0.001 数据立即过期 )

---unlock_kh      主动解一个远程锁

---comm    通用复合接口
---comm2   测试接口
---
---
---
------------------------------------------------------------------
---  key hash 的数据结构：
---		key 123456
---			hash=1    value=vv1
---			hash=2    value=vv2
---			hash=3    value=vv3
---
---	  纯kv的数据结构(可带有expire_time有效期的排重数据)：
---		 key 123456   value=vv1    [可选]expire_time=86400*90(90天有效)
---		 key 123123   value=vv2    [可选]expire_time=86400*30(30天有效)
-------------------------------------------------------------------



local assert     = assert
local require    = require
local bit        = bit
local coroutine  = coroutine
local debug      = debug
local io         = io
local pairs      = pairs
local ipairs     = ipairs
local math       = math
local os         = os
local print      = print
local pcall      = pcall
local xpcall     = xpcall
local rawget     = rawget
local rawset     = rawset
local select     = select
local string     = string
local table      = table
local tonumber   = tonumber
local tostring	 = tostring
local error      = error
local type       = type
local unpack     = unpack
local setmetatable = setmetatable
local getmetatable = getmetatable
local ngx = ngx

local string_char  = string.char
local string_sub   = string.sub
local string_byte  = string.byte
local string_gsub  = string.gsub
local string_find  = string.find
local string_upper = string.upper

local math_fmod   = math.fmod
local math_floor  = math.floor
local math_pow    = math.pow
local math_abs    = math.abs


local uu           = require( "modules.commhm.utils" )
local log          = require( "modules.commhm.log" )
local ns_time      = require( "modules.commhm.time" )
local ns_network   = require( "modules.commhm.network" )
local ns_http      = require( "modules.commhm.resty.http" )

local ns_const_vars  = require("modules.commhm.const_vars")

local ns_config       = require( "modules.data_cache.data_cache_config" )
local gg              = require( "modules.data_cache.data_cache_gg" )
local ns_comm_gg      = require( "modules.data_cache.comm_gg" )
local ns_auth_checker = require( "modules.data_cache.auth_checker" )



local ns_share_dict_checker = require("modules.commhm.framework.share_dict_checker");

local FOO = {
	_VERSION = '0.12.1',
	cmds = {},

	worker_id = ngx.worker.id(),

	DOUBLE_CHECK_RET = 1,   ---是否二次确认结果正确性 kh值
};



FOO.enter = function()
	ngx.ctx.m_params = ngx.req.get_uri_args();

	if  ns_comm_gg.check_ip_allow_by_name() > 0 then
		return
	end

	ngx.header["Carry"] = ngx.ctx.m_params.carry   --防止网络请求串流

	--log.debug( "headers1= " .. table.tostring(ngx.req.get_headers()) );

	local cmd_ = ngx.ctx.m_params.cmd
	if  cmd_  and FOO.cmds[cmd_]  then
		if  ns_auth_checker.check_data_cache_auth( cmd_ ) then
			gg.init()
			FOO.cmds[cmd_]()
			gg.final()
		else
			log.error("auth error:" .. (cmd_ or 'nil') )
		end
	else
		log.error("action error:" .. (cmd_ or 'nil') )
	end

	--log.debug( "headers2= " .. table.tostring(ngx.resp.get_headers()) );

end



-----------------------------通用接口----------------
---POST set_kv  get_kv  hlen  hget_all  del hlist
FOO.cmds.comm = function()
	log.debug( "call dc - comm" )

	--参数在post里
	ngx.req.read_body();
	local args_ = ngx.req.get_post_args();
	uu.var_dump( ngx.ctx.m_params )

	--合并参数
	for k, v in pairs( ngx.ctx.m_params ) do
		args_[k] = v
	end

	ngx.ctx.m_params     = args_
	ngx.ctx.m_params.cmd = ngx.ctx.m_params.func   --顶掉comm

	uu.var_dump( ngx.ctx.m_params )

	if  FOO.cmds[ ngx.ctx.m_params.cmd ] then
		log.debug( "call dc - " .. ngx.ctx.m_params.cmd )
		FOO.cmds[ ngx.ctx.m_params.cmd ]()
	else
		log.error("comm action error:" .. (ngx.ctx.m_params.cmd or 'nil') )
	end

end


--GET 用来测试的接口
FOO.cmds.comm2 = function()
	log.debug( "call dc - comm2" )
	--参数在get里
	ngx.ctx.m_params.cmd = ngx.ctx.m_params.func   --顶掉comm

	uu.var_dump( ngx.ctx.m_params )

	if  FOO.cmds[ ngx.ctx.m_params.cmd ] then
		log.debug( "call dc - " .. ngx.ctx.m_params.cmd )
		FOO.cmds[ ngx.ctx.m_params.cmd ]()
	end
end



-----------------------------------------------------------------
--存储数据
--_M.save = function( self, ssdb_name, key, hash, value, err_call_back, ssdb_delay_ )
FOO.cmds.save = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local hash_   = ngx.ctx.m_params.hash
	local select_ = FOO.private_get_select()

	if  ns_config.debug == 1 then
		local hash_num_ = tonumber(hash_) or 0
		if  hash_num_ <= 0 then
			log.day_list( "ssdb_dc_hash_check", FOO.worker_id, name_, key_, hash_, select_ )
		end
	end

	local value_
	if  ngx.ctx.m_params.value then
		value_ = ngx.ctx.m_params.value   --type is string
	else
		--post
		ngx.req.read_body();
		value_ = ngx.req.get_body_data()
		if  value_ then

			---if len
			if  ngx.ctx.m_params.len then
				local len_ = tonumber( ngx.ctx.m_params.len ) or -1
				if  len_ == #value_ then
					--log.day_list_simple( "ssdb", "dc_client", "len_ok", len_ )
				else
					log.day_list_simple( "ssdb", "dc_client", "error_len", key_, hash_, select_, len_, #value_, value_ )
					uu.return_string( "ERROR_LEN" )
					return
				end
			end

			---if decode base64
			if  ngx.ctx.m_params.isbase64 == '1' then
				local value_64_ = ngx.decode_base64(value_)
				if  value_64_ then
					--log.day_list_simple( "ssdb", "dc_client", "base64_decode_ok", #value_, #value_64_ )
					value_ = value_64_
				else
					log.day_list_simple( "ssdb", "dc_client", "error_base64", key_, hash_, select_, value_ )
					uu.return_string( "ERROR_BASE64", ns_const_vars.ERROR_CODE.PARAM )
					return
				end
			end

			---if decode lua table
			if  ngx.ctx.m_params.islua == '1' then
				local trace_back_ = function( errmsg )
					log.day_list_simple("ssdb", "trace_back", errmsg, debug.traceback());
				end
				local t_
				local function decode_table_()
					t_ = table.loadstring( value_ )
				end
				local ok, errmsg = xpcall(decode_table_, trace_back_)
				if  not ok then
					log.day_list_simple( "ssdb", "xpcall", uu.to_str(ok), uu.to_str(errmsg) )
				end

				if  t_ then
					--log.day_list_simple( "ssdb", "dc_client", "table_ok", #value_ )
				else
					log.day_list_simple( "ssdb", "dc_client", "error_table",  key_, hash_, select_, value_ )
					uu.return_string( "ERROR_LUA_TABLE" )
					return
				end
			end

		else
			log.day_list_simple( "ssdb", "dc_client", "no_value", key_, hash_, select_ )
			uu.return_string( "NO_V", ns_const_vars.ERROR_CODE.PARAM )
			return
		end

	end

	if  not( value_ and #value_ > 0 ) then
		log.day_list_simple( "ssdb", "error_value_len0", key_, hash_, select_, uu.to_str(value_) )
		uu.return_string( "PARAM_VALUE_LEN0", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_save( select_, key_, hash_, value_, FOO.error_exit, ngx.ctx.m_params.ssdb_delay)
		uu.return_string( ret_ or "" )
		ns_comm_gg.add_byte_tj( name_..'_dc', "save_" .. hash_, 0, select_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf name not exit=" .. name_ )
	end

end


-----------------------------------------------------------------
--获取数据
--_M.read = function( self, ssdb_name, key, hash, err_call_back )
FOO.cmds.read = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local hash_   = ngx.ctx.m_params.hash
	local select_ = FOO.private_get_select()

	if  ngx.ctx.m_params.no_cache == 'y' then
		--log.day_list_short( "ssdb", "read_no_shared_cache", name_, select_, key_, hash_ );
		ngx.ctx.no_shared_cache = 'y'   --不存shared缓存
	end

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_read( select_, key_, hash_, FOO.error_exit, ngx.ctx.m_params.read_only )
		if  ret_ then

			if  FOO.DOUBLE_CHECK_RET == 1 then
				-- 对返回的字符串进行二次检查
				local ret_ = ns_comm_gg.check_ret_string_kh( ret_, key_, hash_, name_, 'ssdb' )
				if  ret_ == 1 then
					uu.return_string( "ERROR_KH", ns_const_vars.ERROR_CODE.ERROR_RET_CHECK )
					ssdb_mgr_:API_ssdb_clean_shared_cache( select_, key_, hash_ )
					return
				end
			end

			uu.return_string( ret_ )
			ns_comm_gg.add_byte_tj( name_..'_dc', "read_" .. hash_, #ret_, select_ )
			if  #ret_ <= 0 then
				log.day_list_simple( "ssdb", "error_read_len0", select_, key_, hash_, ret_ )
			end
		end
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


--批量获得数据
-- _M.mread = function( self, ssdb_name, all_kv_list, err_call_back )
FOO.cmds.mread = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local select_ = FOO.private_get_select()

	if  not ( name_ and select_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	--local key_hash_ = all_kv_list[i].key .. "_h" .. all_kv_list[i].hash
	local all_kv_list
	if  ngx.ctx.m_params.kv_list then
		local t_ = uu.split( ngx.ctx.m_params.kv_list, "-" )

		uu.var_dump( t_ )

		if  #t_ > 0 then
			all_kv_list = {}
			for i=1, #t_, 2 do
				all_kv_list[ #all_kv_list + 1 ] = { key=t_[i],  hash=t_[i+1] }
			end
		end

		uu.var_dump( all_kv_list )

	else
		--read post
		ngx.req.read_body();
		local args = ngx.req.get_post_args();
		uu.var_dump( args )
		if  args['kv_list'] then
			all_kv_list = uu.safe_loadstring ( args['kv_list'] )
		end
	end

	if (not  all_kv_list) or #all_kv_list <= 0 then
		uu.return_string( "PARAM_ERROR_LIST0", ns_const_vars.ERROR_CODE.PARAM )
		return
	end


	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_mread( select_, all_kv_list,  FOO.error_exit )
		uu.var_dump( ret_ )
		local len_ = uu.return_table( ret_ )
		ns_comm_gg.add_byte_tj( name_..'_dc', "mread", len_, select_, #all_kv_list )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


-- 删除一个key下的一个hash  ssdb hdel
--_M.del = function( self, ssdb_name, key, hash, err_call_back )
FOO.cmds.del = function()

	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local hash_   = ngx.ctx.m_params.hash
	local select_ = FOO.private_get_select()

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_del( select_, key_, hash_, FOO.error_exit )
		uu.return_string( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


FOO.cmds.hdel = FOO.cmds.del   ---别名



-- 一个key下有多少个hash值
--_M.hlen = function( self, ssdb_name, key, err_call_back)
FOO.cmds.hlen = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local select_ = FOO.private_get_select()

	if  not ( name_ and select_ and key_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_hlen( select_, key_, FOO.error_exit )
		uu.return_string( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


-- 从start开始遍历所有hash key
--_M.hlist = function( self, ssdb_name, start_, err_call_back)
FOO.cmds.hlist = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local start_  = ngx.ctx.m_params.start
	local end_    = ngx.ctx.m_params.h_end  or ""
	local size_   = ngx.ctx.m_params.size
	local select_ = FOO.private_get_select()

	if  not ( name_ and select_ and start_ and size_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_hlist( select_, start_, end_, size_, FOO.error_exit )
		uu.return_string( table.tostring(ret_) )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end




--获得一个key下的所有值
--_M.hget_all = function( self, ssdb_name, key, err_call_back )
FOO.cmds.hget_all = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local select_ = FOO.private_get_select()

	if  not ( name_ and select_ and key_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_hget_all( select_, key_, FOO.error_exit )
		uu.return_string( table.tostring(ret_) )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


--清理缓存，清理后会重新拉取ssdb
FOO.cmds.clean_cache = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local hash_   = ngx.ctx.m_params.hash
	local select_ = FOO.private_get_select()

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		ssdb_mgr_:API_ssdb_clean_shared_cache( select_, key_, hash_ )
		uu.return_string( "OK" )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end
end



---------------------------------kv 独立库
--_M.set_kv = function( self, ssdb_name, key, value, et, err_call_back )
FOO.cmds.set_kv = function()

	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local select_ = FOO.private_get_select()

	local value_  = ngx.ctx.m_params.value

	local et_
	if  ngx.ctx.m_params.et then
		et_ = tonumber(ngx.ctx.m_params.et)
	end

	if  not ( name_ and select_ and key_ and value_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_set_kv( select_, key_, value_, et_, FOO.error_exit )
		uu.return_string( ret_ or "" )
		ns_comm_gg.add_byte_tj( name_..'_dc', "save_kv", 0, select_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end




--_M.get_kv = function( self, ssdb_name, key, err_call_back )
FOO.cmds.get_kv = function()
	local name_   = ngx.ctx.m_params.name      --ssdb name: ma map posting
	local key_    = ngx.ctx.m_params.key
	local select_ = FOO.private_get_select()


	if  not ( name_ and select_ and key_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_get_kv( select_, key_, FOO.error_exit )
		uu.return_string( ret_ )
		ns_comm_gg.add_byte_tj( name_..'_dc', "read_kv", #ret_, select_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end



--加锁 一般read的时候会加锁，特殊情况下可以使用lock_kh来主动获得一个锁
FOO.cmds.lock_kh = function()
	local args_   = ngx.ctx.m_params
	local name_   = args_.name      --ssdb name: ma map posting
	local select_ = FOO.private_get_select()

	local kh_
	if  args_.kh then
		kh_     = args_.kh
	elseif  args_.key and  args_.hash then
		kh_     = args_.key .. '_h' .. args_.hash      --key .. "_h" .. hash
	end

	if  not ( name_ and select_ and kh_ ) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_, err_ = ssdb_mgr_:API_ssdb_try_get_resty_lock( select_, kh_ )
		if  err_ then
			uu.return_table( { ret=ret_, msg=err_ } )
		else
			uu.return_table( { ret=0,    lock_kh_id=ret_ } )
		end
	else
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		log.error( "conf not exit=" .. name_ )
	end
end




--解锁
FOO.cmds.unlock_kh = function()
	local args_   = ngx.ctx.m_params
	local name_   = args_.name                --ssdb name: ma map posting
	local select_ = FOO.private_get_select()

	local kh_
	if  args_.kh then
		kh_     = args_.kh
	elseif  args_.key and  args_.hash then
		kh_     = args_.key .. '_h' .. args_.hash      --key .. "_h" .. hash
	end

	if  not ( name_ and select_ and kh_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local ssdb_mgr_ = gg.ssdbMgrDcList[ name_ ]
	if  ssdb_mgr_ then
		local ret_ = ssdb_mgr_:API_ssdb_unlock_resty_lock( select_, kh_ )
		uu.return_string( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end



------------------------------------------------------
-- 异常退出
FOO.error_exit = function( txt, trace_info_ )
	log.error(txt, trace_info_);
	ns_time.print_cost_all();
	ngx.exit(0);
end


-------------------------------------------private
FOO.private_get_select = function()
	local select_
	if  ngx.ctx.m_params.select then
		select_ = tonumber(ngx.ctx.m_params.select)   --数字
		if  not select_ then
			select_ = ngx.ctx.m_params.select         --comm
		end
	end
	return select_
end




---------定时任务 统计 30s
FOO.time_every_tj = function()
	--1
	ns_comm_gg.print_double_check_tj( 'ssdb' )

	--2
	ns_comm_gg.print_byte_tj( 'ssdb', 'dc' )
end


------------初始化
FOO.init = function()
	ns_comm_gg.timer_every( 30, FOO.time_every_tj )

	---定时检查share_dict剩余空间
	local shared_dict_name_list_ = { "miniw", "ssdb", "map" }
	for i=0, 9 do
		shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = "ssdb"  .. i
		shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = "miniw" .. i
	end

	-- 扫描config中所有的key
	if  ns_config.ssdb then
		for k, v in pairs( ns_config.ssdb ) do
			for i=0, 9 do
				if  type(k) == 'string' then
					shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = k .. i
				end
			end
		end
	end
	ns_share_dict_checker.start_check_by_timer( shared_dict_name_list_, 601, "dc" )

end


FOO.init()
return FOO;
